package sketch.specs;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.BlockComment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.core.dom.NaiveASTFlattener;

import sketch.ast.ASTUtils;
import sketch.util.Checker;

class BlockMappingVisitor extends NaiveASTFlattener {

	final List<Comment> commentToMap;

	final Map<Comment, Block> commentBlocks = new LinkedHashMap<Comment, Block>();
	
	final Map<Block, Comment> blockComments = new LinkedHashMap<Block, Comment>();

	public BlockMappingVisitor(List<Comment> blockComments) {
		Checker.checkNull(blockComments, "The comment to map could not be null!");
		blockComments = ASTUtils.blockComments(blockComments);
		this.checkCommentValidity(blockComments);
		this.commentToMap = blockComments;
	}

	public Map<Comment, Block> getCommentMappings() {
		//check the results, and prune out some invalid cases like:
		// { /*@ChooseOne*/  /*@ChooseAll*/, the second is not valid, and should be removed
		// }
		this.checkAndPruningResults();
		return this.commentBlocks;
	}
	
	public Map<Block, Comment> getBlockMappings() {
		this.checkAndPruningResults();
		return this.blockComments;
	}
	
	@Override
	public boolean visit(Block block) {

		int blockStartingPos = block.getStartPosition();
		int blockEndingPos = block.getStartPosition() + block.getLength();

		Comment closestComment = null;
		int closestDistance = Integer.MAX_VALUE;
		for (Comment comment : commentToMap) {
			int commentStartPos = comment.getStartPosition();
			int commentEndPos = comment.getStartPosition()
					+ comment.getLength();
			if (commentStartPos < blockStartingPos
					|| commentEndPos > blockEndingPos) {
				continue;
			}
			// check the current block instance
			int distanceToBlock = commentStartPos - blockStartingPos;
			if (distanceToBlock < closestDistance) {
				closestDistance = distanceToBlock;
				closestComment = comment;
			}
		}

		// check the existing map;
		if (closestComment != null) {
			if (this.commentBlocks.containsKey(closestComment)) {
				Block existingClosest = this.commentBlocks.get(closestComment);
				if (existingClosest.getStartPosition() < block
						.getStartPosition()) {
					this.commentBlocks.put(closestComment, block);
				}
			} else {
				this.commentBlocks.put(closestComment, block);
			}
		}

		return super.visit(block);
	}
	
	private void checkCommentValidity(List<Comment> comments) {
		for(Comment comment : comments) {
			Checker.checkTrue(comment instanceof BlockComment, "Only block comments like /*@ChooseOne*/" +
					" is valid, while the given comment is: " + comment.getClass() + " type!");
		}
	}
	
	private void checkAndPruningResults() {
		//no need to check
		if(this.blockComments.size() == this.commentBlocks.size()) {
			return;
		}
		
		//have not fulfill the other map
		Set<Block> blocks = new HashSet<Block>();
		blocks.addAll(this.commentBlocks.values());
		if(blocks.size() == this.commentBlocks.size()) {
			for(Comment comment : this.commentBlocks.keySet()) {
				Block block = this.commentBlocks.get(comment);
				if(this.blockComments.containsKey(block)) {
					throw new RuntimeException("Should not be here, block: " + block
							+ " is already in the map.");
				}
				this.blockComments.put(block, comment);
			}
			return;
		}
		
		//comment to remove
		List<Comment> commentToRemove = new LinkedList<Comment>();
		//we need to prune out redundant blocks
		for(Comment comment : this.commentBlocks.keySet()) {
			Block block = this.commentBlocks.get(comment);
			if(!this.blockComments.containsKey(block)) {
				this.blockComments.put(block, comment);
			} else {
				Comment alreadyInComment = this.blockComments.get(block);
				if(alreadyInComment.getStartPosition() > comment.getStartPosition()) {
					this.blockComments.put(block, comment);
					//the alreadyInComment should be removed
					commentToRemove.add(alreadyInComment);
				} else {
					commentToRemove.add(comment);
				}
			}
		}
		
		for(Comment c : commentToRemove) {
			this.commentBlocks.remove(c);
		}
		
		Checker.checkTrue(this.blockComments.size() == this.commentBlocks.size(), "The size of blockComments: "
				+ this.blockComments.size() + "  should be equal to the size of commentBlocks: "
				+  this.commentBlocks.size());
	}
}
